package com.hiray.taglayoutmanager.layoutManager;

import android.graphics.Point;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by hiray on 2017/12/13.
 * LayoutManager managing mess item regardless of its size
 *
 * @author hiray
 */

public class BlockLayoutManager extends RecyclerView.LayoutManager {

    private static final String TAG = "BlockLayoutManager";
    private Detector detector = new Detector();

    public BlockLayoutManager() {
        setAutoMeasureEnabled(true);
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (state.getItemCount() == 0 || state.isPreLayout())
            return;
        fill(recycler, state, 0);
        Log.i(TAG, "onLayoutChildren: ");
    }


    private int fill(RecyclerView.Recycler recycler, RecyclerView.State state, int dy) {
        if (getChildCount() == 0 && state.isPreLayout())
            return 0;
        int itemCount = getItemCount();
        List<View> itemViews = new ArrayList<>();
        //measure child first
        detachAndScrapAttachedViews(recycler);
//        detector.updateWhenLineCrossThroughAtLevel(dy);
        for (int i = 0; i < itemCount; i++) {
            View itemView = recycler.getViewForPosition(i);
            measureChild(itemView, 0, 0);
            itemViews.add(itemView);
        }

        detector.reset();
        //layout child according to the measured info
        for (int i = 0; i < itemCount; i++) {
            View itemView = itemViews.get(i);
            detectAndLayout(i, itemView);
        }

        return dy;
    }

    private void detectAndLayout(int position, View itemView) {

        int w = itemView.getMeasuredWidth();
        int h = itemView.getMeasuredHeight();
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) itemView.getLayoutParams();
        int lMargin = lp.leftMargin;
        int tMargin = lp.topMargin;
        int rMargin = lp.rightMargin;
        int bMargin = lp.bottomMargin;
        Cave sunkenHole = detector.detectFirstAvailableCave(w + lMargin + rMargin);
        // we use the vertical  dy to start ensure the anchor
//        if (BuildConfig.DEBUG)
//            Log.i(TAG, "detectAndLayout--- position:" + position + "w:" + w + "  h:" + h);
        if (sunkenHole != null) {
            sunkenHole.setUsedWidth(w + lMargin);
            detector.reManage(sunkenHole, w + lMargin + rMargin, h + tMargin + bMargin);
        } else {
//            if (BuildConfig.DEBUG)
//                Log.i(TAG, "detectAndLayout: NULLLL");
            return;
        }
        addView(itemView);
        layoutDecorated(itemView, sunkenHole.left + lMargin,
                sunkenHole.top + tMargin,
                sunkenHole.left + w + lMargin,
                sunkenHole.top + h + tMargin);
    }

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    class Detector {
        //border line
        LinkedList<Point> line = new LinkedList<>();


        void reset() {
            line.clear();
            line.add(new Point(0, getHeight()));
            line.add(new Point(0, 0));
            line.add(new Point(getWidth(), 0));
            line.add(new Point(getWidth(), getHeight()));
        }

        @Nullable
        private Cave detectFirstAvailableCave(int width) {
           /*
            *   p            horizontalP
            *   ——————————————
            *   |            |
            *   |            |
            *   |            |
            *   |            |
            *   |            |
            *  beforeP       |
            *                |
            *                verticalP
            *
            *
            *
            */

            int size = detector.line.size();
            List<Point> points = detector.line;
            Cave propSunkenHole = null;
            for (int i = 1; i < size - 2; i++) {
                Point p = points.get(i);
                Point beforeP = points.get(i - 1);
                Point horizontalP = points.get(i + 1);
                Point verticalP = points.get(i + 2);

                //check if detect a hole that fits the item
                if (isCaveAndFit(p, beforeP, horizontalP, verticalP, width)) {
                    Cave hole = createACaveWithPoint(i, p, beforeP, horizontalP, verticalP);
                    if (propSunkenHole == null || propSunkenHole.top > hole.top)
                        propSunkenHole = hole;
                }

            }

            return propSunkenHole == null ? detectComplexerCave(6, width) : propSunkenHole;
        }

        /**
         * @param dimension points number that form a cave
         *                  typically, four points make a cave(dimension is 4),
         *                  but four-point cave cannot wrap a item,so we need bigger cave that made of
         *                  more points(step size is 2)
         * @return {@link Cave} sunkenHole
         */

        @Nullable
        private Cave detectComplexerCave(int dimension, int w) {
            /*
             *behindStartP          beforeEndP
             *   _________     ______
             *   |       |     |    |
             *   |       |_____|    |
             *   |                  |
             *   |                  |
             *   |                  |
             *  startP              |
             *                      |
             *                     endP
             *
             *
             *
             */
            int size = line.size();
            if (dimension > size)
                return null;
            else {
                // check i and i+dimension to
                int end = size - dimension;
                for (int i = 0; i <= end; i += 2) {

                    Point startP = line.get(i);
                    Point behindStartP = line.get(i + 1);

                    Point endP = line.get(i + dimension - 1);
                    Point beforeEndP = line.get(i + dimension - 2);


                    int holeHeight = Math.min(startP.y - behindStartP.y, endP.y - beforeEndP.y);
                    int left = startP.x;
                    int width = endP.x - startP.x;
                    int top = -1;

                    if (behindStartP.x == startP.x && beforeEndP.x == endP.x && w <= endP.x - startP.x) {
                        for (int j = i + 1; j < dimension - 1; j += 2) {
                            int y = line.get(j).y;
                            if (y > top)
                                top = y;

                        }
                        //必须满足Hole的特征（凹陷）
                        if (top != -1 && top < Math.min(startP.y, endP.y))
                            return new Cave(i, left, top, width, holeHeight, startP.y == endP.y);
                    }

                }

                return detectComplexerCave(dimension + 2, w);

            }
        }

        @SuppressWarnings("unused")
        private void updateWhenLineCrossThroughAtLevel(int dy) {
            //目前考虑正反两种方向,更新后低过0的赋值0,高过h的赋值h
            int size = line.size();
            for (int i = 1; i < size; i++) {
                Point p = line.get(i);
                Point pa = line.get(i + 1);
                int diff = p.y - dy;
                if (diff <= 0) {
                    p.set(p.x, 0);
                    pa.set(pa.x, 0);
                } else {
                    p.set(p.x, diff);
                    pa.set(pa.x, diff);
                }
            }
            mergeRepeatPoint();
        }

        private void mergeRepeatPoint() {
//            int size = line.size();
//            if (size <= 4)
//                return;
//            for (int i = size - 3; i >= 1; i++) {
//                Point p = line.get(i);
//                Point pb = line.get(i - 1);
//            }
        }

        private Cave createACaveWithPoint(int index, Point p, Point beforeP, Point horizontalP, Point verticalP) {
            int lh = verticalP.y - horizontalP.y;
            int rh = beforeP.y - p.y;
            return new Cave(index, p.x, p.y, horizontalP.x - p.x, Math.min(lh, rh), lh == rh);
        }

        private boolean isCaveAndFit(Point p, Point beforeP, Point horizontalP, Point verticalP,
                                     int width) {

            return p.y == horizontalP.y
                    && p.x == beforeP.x
                    && beforeP.y > p.y
                    && verticalP.x == horizontalP.x
                    && verticalP.y > horizontalP.y
                    && horizontalP.x - p.x >= width;
        }

        //update the already-changed point ,and add the newly generated point data
        void reManage(Cave cave, int w, int h) {

            int xStart = cave.left;
            int xEnd = cave.left + cave.usedWidth;
            int level = cave.top;
            int size = line.size();
            int index = -1;
            for (int i = size - 1; i >= 0; i--) {
                Point p = line.get(i);
                int x = p.x;
                int y = p.y;
                if (x >= xStart && x <= xEnd && y <= level) {
                    line.remove(i);
                    index = i;
                }
            }

            Point leftAnchor = line.get(index - 1);
            Point rightAnchor = line.get(index);

            int caveWidth = cave.width;
            int caveHeight = cave.height;
            boolean isWidthFit = caveWidth == w;
            boolean isHeightFit = cave.isBottle && (caveHeight == h);

            Point future1 = new Point(cave.left, level + h);
            Point future2 = new Point(cave.left + w, level + h);
            Point future3 = new Point(cave.left + w, level);

            if (isWidthFit) {
                //no adding future2
                if (isHeightFit) {
                    if (index != 0) {
                        //remove the index point
                        line.remove(leftAnchor);
                    }
                    // no adding any point,they just merge!
                } else {
                    if (leftAnchor.y == future1.y) {
                        line.remove(leftAnchor);
                    } else {
                        line.add(index, future1);
                        index++;
                    }

                    if (rightAnchor.y == future2.y) {
                        // no adding future3 and remove rightAnchor point
                        //because now there is three points at same level
                        line.remove(rightAnchor);
                    } else {
                        line.add(index, future2);
                    }

                }
            } else {
                if (isHeightFit) {
                    //update the left anchor
                    line.remove(leftAnchor);
                } else {
                    //该点可能是悬空的，需要更新
                    if (leftAnchor.y != future1.y) {
                        line.add(index++, future1);
                        line.add(index++, future2);

                    } else {
                        line.remove(leftAnchor);
                        line.add(index - 1, future2);
                    }

                    if (rightAnchor.y != future3.y) {
                        future3.set(future3.x, rightAnchor.y);
                    }
                    line.add(index, future3);

                }
            }
        }
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        return fill(recycler, state, dy);
    }

    @Override
    public boolean canScrollVertically() {
        return true;
    }

    class Cave {

        /*
         *  the four points of an typical sunkenHole
         *  p2                p3
         *  __________________
         * 丨                丨
         * 丨                丨
         * 丨                丨
         * 丨                丨
         * 丨                丨
         * 丨                丨
         * p1                丨
         *                   丨
         *                   丨p4
         */


        //p1
        int index;

        //left distance to its parent'left
        int left;
        //top distance to its parent's top
        int top;

        int width;
        int height;

        int usedWidth;

        //the left height is high as right height;
        boolean isBottle;

        Cave(int index, int left, int top, int width, int height) {
            this(index, left, top, width, height, false);
        }

        Cave(int index, int left, int top, int width, int height, boolean isBottle) {
            this.index = index;
            this.left = left;
            this.top = top;
            this.width = width;
            this.height = height;
            this.isBottle = isBottle;
        }

        void setUsedWidth(int usedWidth) {
            this.usedWidth = usedWidth;
        }

    }

    @Override
    public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
        detector.reset();
    }

    @Override
    public void onItemsChanged(RecyclerView recyclerView) {
        super.onItemsChanged(recyclerView);
        detector.reset();
    }

    @Override
    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, Object payload) {
        super.onItemsUpdated(recyclerView, positionStart, itemCount, payload);
        detector.reset();

    }

    @Override
    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
        super.onItemsRemoved(recyclerView, positionStart, itemCount);
        detector.reset();
    }
}
